/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implementation of class parse tree.
 *
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */

#include <stdlib.h>

#include "../include/amcc_error.h"
#include "../include/amcc_log.h"
#include "../include/tree.h"

// singleton handler to free any annotation in tree node
static t_treefunc_free_annotation func_freeAnnotation;

/** initialize inner data of tree module */
int tree_module_init() {
    func_freeAnnotation = NULL;
    return 0;
}

/** clean inner data of tree module */
void tree_module_clean(){

}

t_treefunc_free_annotation
TREEFUNC_SET_free_annotation (t_treefunc_free_annotation newHandler)
{
    t_treefunc_free_annotation  oldHandler = func_freeAnnotation;
    func_freeAnnotation = newHandler;
    return oldHandler;
}



bool tree_is_leaf(t_tree_ptr pThis) {
    return (NULL == pThis) || list_is_empty(pThis->children);
}


bool tree_is_root(t_tree_ptr pThis) {
    return (NULL != pThis) && NULL == pThis->parent;
}


t_tree_ptr tree_get_parent(t_tree_ptr pThis) {
    return (NULL == pThis) ? NULL : pThis->parent;
}


t_list_ptr tree_get_children(t_tree_ptr pThis) {
    return (NULL == pThis) ? NULL : pThis->children;
}


int  tree_get_childCount(t_tree_ptr pThis) {
    return (NULL == pThis) ? 0 : list_size(pThis->children);
}


t_tree_ptr tree_get_child(t_tree_ptr pThis, int index) {
    if (NULL == pThis) return NULL;
    return (t_tree_ptr)list_get_value( pThis->children , index );
}


int tree_get_productionId(t_tree_ptr pThis) {
    return (NULL == pThis) ? INT_MIN : pThis->marker.productionId;
}


bool tree_is_nonTerminal(t_tree_ptr pThis) {
    return (NULL == pThis) ? false : (pThis->marker.productionId > 0);
}


bool tree_is_epsilon(t_tree_ptr pThis) {
    return (NULL == pThis) || (pThis->marker.productionId == 0);
}


bool tree_is_terminal(t_tree_ptr pThis) {
    return (NULL == pThis) ? false : (pThis->marker.productionId < 0);
}


t_tree_marker_ptr tree_get_marker(t_tree_ptr pThis) {
    return (NULL == pThis) ? NULL : &(pThis->marker);
}


const char * tree_get_markText(t_tree_ptr pThis) {
    if ((NULL == pThis) || tree_is_epsilon(pThis) )
        return EPSILON_TEXT;

    if ( tree_is_nonTerminal(pThis) )
        return pThis->marker.instance.nonTerminal->ntName;

    t_terminal_ptr  terminalPtr = pThis->marker.instance.terminal;
    if (NULL != terminalPtr->tokenPtr)
        return token_get_text(terminalPtr->tokenPtr);
    else
        return "<null-text>";
}


t_nonterminal_ptr  tree_get_nonTerminal(t_tree_ptr pThis) {
    if( tree_is_nonTerminal(pThis) )
        return pThis->marker.instance.nonTerminal;
    else
        return NULL;
}


t_terminal_ptr  tree_get_terminal(t_tree_ptr pThis) {
    // pThis should be marked with a terminal  or epsilon
    if(  ! tree_is_nonTerminal(pThis) )
        return pThis->marker.instance.terminal;
    else
        return NULL;
}


t_token_ptr tree_get_token(t_tree_ptr pThis) {
    t_terminal_ptr p = tree_get_terminal(pThis);
    return (NULL != p) ? (p->tokenPtr) : NULL;
}


EnumTokenKind  tree_get_tokenKind(t_tree_ptr pThis) {
    t_terminal_ptr p = tree_get_terminal(pThis);
    return (NULL != p) ? token_get_kind(p->tokenPtr) : TK_ERROR;
}


bool tree_is_identifier(t_tree_ptr pThis) {
    return (TK_ID == tree_get_tokenKind(pThis) );
}


bool tree_is_typeName(t_tree_ptr pThis) {
    t_terminal_ptr p = tree_get_terminal(pThis);
    return (NULL != p) ? token_is_type(p->tokenPtr) : false;
}

/*
 * Whether this node is marked with ID, and its parent is marked
 * with the expected non-terminal.
 *
 * This func is ONLY invoked in this file.
 */
static bool
treeParent_is_nonTerminal(t_tree_ptr node, EnumNonTerminal expectedNT) {
    EnumTokenKind kind = tree_get_tokenKind(node);
    if ( TK_ID != kind || NULL == node->parent )
        return false;

    t_nonterminal_ptr parentNT = tree_get_nonTerminal(node->parent);
    if ( NULL == parentNT )
        return false;
    else
        return ( expectedNT == parentNT->ntId );
}


bool tree_is_funcNameReference(t_tree_ptr pThis) {
    return treeParent_is_nonTerminal(pThis, NT_functionCall);
}


bool tree_is_funcNameDefinition(t_tree_ptr pThis) {
//    return treeParent_is_nonTerminal(pThis, NT_funcDeclaration);
    bool parentIsFuncDefine = treeParent_is_nonTerminal(pThis, NT_funcDeclaration);
    if ( ! parentIsFuncDefine )
        return false;

    t_list_ptr brothers = tree_get_children(pThis->parent);
    t_tree_ptr lastBrother = list_get_last_value(brothers);

    // has function body
    return ( TK_SEMI != tree_get_tokenKind(lastBrother) );
}


bool tree_is_funcNameNonDefinition(t_tree_ptr pThis) {
    bool parentIsFuncDefine = treeParent_is_nonTerminal(pThis, NT_funcDeclaration);
    if ( ! parentIsFuncDefine )
        return false;

    t_list_ptr brothers = tree_get_children(pThis->parent);
    t_tree_ptr lastBrother = list_get_last_value(brothers);

    // no function body
    return TK_SEMI == tree_get_tokenKind(lastBrother) ;
}


bool tree_is_varNameDefinition(t_tree_ptr pThis) {
    return treeParent_is_nonTerminal(pThis, NT_directDeclarator);
}

bool tree_is_varNameReference(t_tree_ptr pThis) {
    return treeParent_is_nonTerminal(pThis, NT_leftValue);
}

bool tree_is_labelNameDefinition(t_tree_ptr pThis) {
    return treeParent_is_nonTerminal(pThis, NT_labeledStatement);
}

bool tree_is_labelNameReference(t_tree_ptr pThis) {
    return treeParent_is_nonTerminal(pThis, NT_jumpStatement);
}

extern t_position_ptr token_zero_position();

t_position  tree_get_startPosition(t_tree_ptr pThis) {
    if (NULL == pThis)
        return *token_zero_position();

    if (pThis->startPosition.line > 0)
        return pThis->startPosition;

    t_token_ptr pToken = tree_get_token(pThis);
    if (NULL != pToken) {
        pThis->startPosition = *token_get_position(pToken);
        return pThis->startPosition;
    }

    // get the first leaf node for nodes marked with a non-terminal
    t_tree_ptr child1 = tree_get_child(pThis, 0);
    while ( NULL == (pToken = tree_get_token(child1)) ){
        child1 = tree_get_child(child1, 0);
    }

    if (NULL != pToken) {
        pThis->startPosition = *token_get_position(pToken);
        return pThis->startPosition;
    }

    return * token_zero_position();
}


t_position  tree_get_stopPosition(t_tree_ptr pThis) {
    if (NULL == pThis)
        return *token_zero_position();

    if (pThis->stopPosition.line > 0)
        return pThis->stopPosition;

    t_token_ptr pToken = tree_get_token(pThis);
    if (NULL != pToken) {
        pThis->stopPosition = * token_get_position(pToken);
        if (TK_EPSILON != token_get_kind(pToken)) {
            pThis->stopPosition.column += token_get_length(pToken);
            pThis->stopPosition.column -= 1;
        }
        return pThis->stopPosition;
    }

    // get the last leaf node for nodes marked with a non-terminal
    t_tree_ptr child1 = tree_get_child(pThis, tree_get_childCount(pThis) -1);
    while ( NULL == (pToken = tree_get_token(child1)) ){
        child1 = tree_get_child(child1, tree_get_childCount(child1) -1);
    }

    if (NULL != pToken) {
        pThis->stopPosition = * token_get_position(pToken);
        if (TK_EPSILON != token_get_kind(pToken)) {
            pThis->stopPosition.column += token_get_length(pToken);
            pThis->stopPosition.column -= 1;
        }
        return pThis->stopPosition;
    }

    return * token_zero_position();
}

struct symtable_entry *  tree_get_symbolEntry(t_tree_ptr pThis) {
    t_terminal_ptr terminalPtr = tree_get_terminal(pThis);
    return (NULL == terminalPtr) ? NULL : terminalPtr->symEntryPtr;
}

void tree_set_symbolEntry(t_tree_ptr pThis, struct symtable_entry * entry) {
    t_terminal_ptr terminalPtr = tree_get_terminal(pThis);
    if (NULL == terminalPtr)
        return;
    terminalPtr->symEntryPtr = entry;
}

t_annotation_ptr  tree_get_annotation(t_tree_ptr pThis) {
    return (NULL == pThis) ? NULL : pThis->pAnnotation;
}

t_annotation_ptr tree_set_annotation(t_tree_ptr pThis, t_annotation_ptr pAnnotation) {
    if (NULL == pThis)
        return NULL;

    t_annotation_ptr last = pThis->pAnnotation;
    pThis->pAnnotation = pAnnotation;
    return last;
}



static void tree_destroy_INNER__(t_tree_ptr root)   ;

void tree_destroy(t_tree_ptr root) {
    tree_destroy_INNER__(root);
}

void parse_tree_destroy(t_tree_ptr pThis) {
    log_trace("DESTROY-ing parse tree with root %tx ...", pThis);
    tree_destroy_INNER__(pThis);
    log_debug("DESTROY-ed  parse tree with root %tx", pThis);
}



// -----------------------------------------------------------------------
//  Internal  Functions  only  for  parser
//

void tree_reset_production(t_tree_ptr ntNode, int prdId) {
    if (NULL == ntNode || prdId <= 0 || ! tree_is_nonTerminal(ntNode))
        return;
    ntNode->marker.productionId = prdId;
}


t_tree_ptr  tree_new_parent(EnumNonTerminal nt, int productionId) {
    t_nonterminal_ptr  pNT = nonterm_query_define(nt);
    if (NULL == pNT) {
        ERROR_REPORT_BUG("No definition of EnumNonTerminal %d", nt);
        return NULL;
    }
    if(productionId <= 0 ) {
        ERROR_REPORT_BUG("Bad productionId %d for creating a parent node"
                         " marked nonTerminal [%s]", productionId, pNT->ntName);
        return NULL;
    }

    t_tree_ptr  node =  (t_tree_ptr)calloc(1, sizeof(*node));
    if (NULL == node) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_list_ptr children = list_new((t_listfunc_free_appdata) tree_destroy_INNER__);
    if (NULL == children) {
        free ( node );
        ERROR_REPORT_MALLOC();
        return NULL;
    }
    node->children = children;

    node->marker.productionId = productionId;
    node->marker.instance.nonTerminal = pNT;
    return node;
}


bool  tree_add_child (t_tree_ptr parent, t_tree_ptr child) {
    if (NULL != list_append(parent->children, child)){
        child->parent = parent;
        return true;
    }
    return false;
}


t_tree_ptr  tree_new_leaf (t_token_ptr tokenPtr) {
    if (NULL == tokenPtr)
        return NULL;

    t_tree_ptr  node = (t_tree_ptr)calloc(1, sizeof(*node));
    if (NULL == node) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_terminal_ptr terminalPtr =
            (t_terminal_ptr)calloc(1, sizeof(*terminalPtr));
    if (NULL == terminalPtr) {
        ERROR_REPORT_MALLOC();
        free(node);
        return NULL;
    }
    terminalPtr->tokenPtr = tokenPtr;

    int myPrdId = token_get_kind(tokenPtr);
    if (myPrdId >  0) {
        myPrdId = 0 - myPrdId;
    } else {
        ERROR_REPORT_BUG("Bad token-kind %d for creating a leaf node"
                         " marked terminal [%s]",
                         myPrdId,  token_get_text(tokenPtr) );
        myPrdId = -1;
    }

    node->marker.productionId = myPrdId;
    node->marker.instance.terminal = terminalPtr;
    return node;
}



extern t_token_ptr token_new_epsilon(t_position_ptr positionPtr);

t_tree_ptr  tree_new_epsilon (t_position_ptr positionPtr) {
    t_tree_ptr  node = (t_tree_ptr)calloc(1, sizeof(*node));
    if (NULL == node) {
        ERROR_REPORT_MALLOC();
        return NULL;
    }

    t_token_ptr epsilonToken = token_new_epsilon(positionPtr);
    if (NULL == epsilonToken) {
        ERROR_REPORT_MALLOC();
        free(node);
        return NULL;
    }

    // use this data to store a EPSILON text, position, ...
    t_terminal_ptr terminalPtr =
            (t_terminal_ptr)calloc(1, sizeof(*terminalPtr));
    if (NULL == terminalPtr) {
        ERROR_REPORT_MALLOC();
        free(node);
        return NULL;
    }
    terminalPtr->tokenPtr = epsilonToken;

    node->marker.productionId = 0;
    node->marker.instance.terminal = terminalPtr;
    return node;
}


static int callback_free_subtree (t_list_app_value app_data,
                                  void * placeholder)
{
    tree_destroy_INNER__((t_tree_ptr) app_data);
    return 0;
}

// internal implementation for tree_destroy()
static void tree_destroy_INNER__ (t_tree_ptr root) {
    if ( NULL == root ) return;
    if ( NULL != root->children ) {

        // if list_init() without any free-handler to app-data
        if ( false == list_has_freefunc(root->children) )
            list_foreach(root->children, callback_free_subtree, NULL);

        list_destroy( root->children );
    }

    if (NULL != root->pAnnotation && NULL != func_freeAnnotation) {
        // log_trace("XXX  free annotation %tx, node %tx", root->pAnnotation, root);
        func_freeAnnotation ( root->pAnnotation );
    }

    if ( tree_is_terminal(root) )
        free (root->marker.instance.terminal);

    free ( root );
}


extern t_token_ptr token_new_pseudo (const char * txt, EnumTokenKind kind);
t_tree_ptr  tree_new_pseudo (const char * txt, EnumTokenKind kind) {
    t_token_ptr pseudoToken = token_new_pseudo(txt, kind);
    t_tree_ptr  pseudoNode  = tree_new_leaf(pseudoToken);
    return pseudoNode;
}



bool tree_is_markedByNT(t_tree_ptr pThis, EnumNonTerminal nt) {
    t_nonterminal_ptr ntPtr = tree_get_nonTerminal(pThis);
    return NULL != ntPtr && nt == ntPtr->ntId;
}

bool tree_is_markedByToken(t_tree_ptr pThis, EnumTokenKind tk) {
    return tk == tree_get_tokenKind(pThis);
}
